package ch.fusun.baron.server.rmi;

import java.io.IOException;
import java.util.List;

import ch.fusun.baron.core.command.GameCommand;
import ch.fusun.baron.core.initialization.GameInitializationUtil;
import ch.fusun.baron.core.initialization.GameInitializer;
import ch.fusun.baron.core.injection.Configure;
import ch.fusun.baron.core.injection.Inject;
import ch.fusun.baron.core.injection.ReInjector;
import ch.fusun.baron.core.rmi.KryoUtil;
import ch.fusun.baron.core.rmi.User;
import ch.fusun.baron.core.service.UserService;
import ch.fusun.baron.data.DataRegistry;
import ch.fusun.baron.data.DataUpdate;
import ch.fusun.baron.turn.TurnRegistry;
import ch.fusun.baron.turn.TurnService;
import ch.fusun.baron.turn.TurnTask;

import com.esotericsoftware.kryonet.Connection;
import com.esotericsoftware.kryonet.Listener;
import com.esotericsoftware.kryonet.Server;

/**
 * This is the server class. It handles all incoming commands and triggers
 * turns.
 */
public class GameServerImpl implements GameServer {
	@Configure(value = "4000")
	private int DELAY;

	private transient GameStateService gameStateService;

	@Inject
	private transient GameInitializationUtil initializationUtil;

	@Inject
	private transient DataRegistry dataRegistry;

	private transient UserService userService;
	private transient TurnService turnService;

	/**
	 * The Kryonet server
	 */
	private transient Server server;

	/**
	 * The Game server constructor
	 */
	public GameServerImpl() {
		// ReInjector.getInstance().reInject(this);
	}

	/**
	 * @param userService
	 *            The user service
	 */
	@Inject
	public void setUserService(UserService userService) {
		this.userService = userService;
		userService.addDataListener(this);
	}

	/**
	 * @param turnService
	 *            The turn service
	 */
	@Inject
	public void setTurnService(TurnService turnService) {
		this.turnService = turnService;
		this.turnService.addDataListenerIfNotAlready(this);
		if (gameStateService == null) {
			gameStateService = ReInjector.getInstance().getInstanceForClass(
					GameStateService.class);
		}
		if (ServerPhase.GAME.equals(gameStateService.current())) {
			turnService.start(DELAY);
		}
	}

	/**
	 * SDZ We need a @Before(class)...
	 * 
	 * @param gameStateService
	 *            sets the gameStateService
	 */
	@Inject
	public void setGameStateService(GameStateService gameStateService) {
		this.gameStateService = gameStateService;
	}

	@Override
	public void userLoggedIn(User user) {
		// Do nothing
	}

	private void fullUpdateUser(Connection connection) {
		synchronized (dataRegistry) {
			List<DataUpdate> fullUpdate = dataRegistry.getFullUpdate();
			for (DataUpdate update : fullUpdate) {
				server.getConnections();
				connection.sendTCP(update);
			}
		}
	}

	@Override
	public void startup() {
		server = new Server();
		for (Class<?> clazz : KryoUtil.getInstance()
				.getClassesForRegistration()) {
			server.getKryo().register(clazz);
		}

		server.start();
		try {
			server.bind(54555, 54555);
		} catch (IOException e) {
			e.printStackTrace();
		}
		server.addListener(new Listener() {
			@Override
			public synchronized void received(Connection connection,
					Object object) {
				System.out.println(object);
				if (object instanceof GameCommand) {
					synchronized (GameServerImpl.this) {
						tryExecuteCommand(object);
					}
				}
			}

			private void tryExecuteCommand(Object object) {
				try {
					GameCommand command = (GameCommand) object;
					ReInjector.getInstance().reInject(command);
					if (command.isAllowed()) {
						command.execute();
						updateUsers();
					} else {
						System.err.println(command + " not allowed"); //$NON-NLS-1$
					}
				} catch (Exception e) {
					System.err.println("Error occured while executing " //$NON-NLS-1$
							+ object + e.getMessage());
				}
			}

			@Override
			public void connected(Connection connection) {
				fullUpdateUser(connection);
			}

			@Override
			public void disconnected(Connection connection) {
				userService.logout(connection.getID());
			}
		});
		gameStateService.transitionTo(ServerPhase.LOBBY);
	}

	@Override
	public void startGame() {
		if (gameStateService.current().startGameAllowed()) {
			synchronized (GameServerImpl.this) {
				gameStateService.transitionTo(ServerPhase.GAMESTARTUP);
				initializeGame();
				updateUsers();
				turnService.start(DELAY);
				gameStateService.transitionTo(ServerPhase.GAME);
			}
		}
	}

	private void initializeGame() {
		List<GameInitializer> initializers = initializationUtil
				.getInitializers();
		for (GameInitializer initializer : initializers) {
			initializer.initialize();
		}
	}

	/**
	 * Updates all the users with the data on the server
	 */
	private void updateUsers() {
		synchronized (dataRegistry) {
			List<DataUpdate> updates = dataRegistry.getUpdates();
			if (!updates.isEmpty()) {
				for (Connection client : server.getConnections()) {
					for (DataUpdate update : updates) {
						client.sendTCP(update);
					}
				}
			}
			dataRegistry.clearUpdates();
		}
	}

	@Override
	public void onTurn() {
		synchronized (GameServerImpl.this) {
			for (TurnTask turnTask : TurnRegistry.getInstance().getTurnTasks()) {
				turnTask.execute();
			}
			System.out.println("turn"); //$NON-NLS-1$
			updateUsers();
		}
	}

	@Override
	public void saveState(String path) {
		dataRegistry.persist(path);
	}

	@Override
	public void loadState(String path) {
		dataRegistry.load(path);
	}

	@Override
	public void dataChanged(DataUpdate update) {
		// Do nothing
	}

	@Override
	public void userLoggedOut(User user) {
		// Do nothing
	}

	@Override
	public void userCreated(User user) {
		// Do nothing
	}

}